using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Autobot
{
    // Size of the brush. Affects how many blocks surrounding the brush's location are changed
    public enum BrushSize : int
    {
        SMALL = 0,
        MEDIUM,
        LARGE,
        NUM_SIZES
    }

    // What to set the block state to when the user tries to draw to the map
    public enum BrushState
    {
        NONE    = BlockState.UNKNOWN,
        FREE    = BlockState.FREE,
        BLOCKED = BlockState.BLOCKED
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class MapEditor : Microsoft.Xna.Framework.Game
    {
        //Graphics device
        GraphicsDeviceManager graphics;

        //A string to write to the window
        //1st line of instructions, second line of instructions
        // and the state strings
        ShowString instructionsString1;
        ShowString instructionsString2;

        // If the name doesn't have 'Var' at the end, then it is just text.
        // If the name contains Var at the end, then it's value will be updated.
        ShowString currentBrush;
        ShowString currentBrushVar;
        ShowString currentBrushSize;
        ShowString currentBrushSizeVar;

        //Need to have a map and a filename to save to
        private VirtualMap map;
        private string filename;

        //Information about the last state of keyboard and mouse
        private KeyboardState lastKeyboardInput;
        private MouseState lastMouseInput;

        //Information about the brush size
        private BrushSize brushSize;

        // Flag if the map should be redrawn
        private bool redrawMap;

        // Brush state for what to draw to the map
        private BrushState brushState;

        /// <summary>
        /// Create a map editor that initializes all internal variables
        /// Sets the window size, prepares the map, and inits all display strings
        /// </summary>
        /// <param name="RealSize">The size of the map</param>
        /// <param name="RealBlockSize">The size of the individual blocks</param>
        /// <param name="WindowSize">The size of the output window</param>
        /// <param name="Filename">File name to save to and load from. Automatically load the map if one exists</param>
        public MapEditor(Vector2 RealSize, Vector2 RealBlockSize, Vector2 WindowSize, string Filename)
        {
            // Load up the graphics manager
            graphics = new GraphicsDeviceManager(this);

            // Set the window size according to input recieved, leave some room for instructions in the top 20 pixels of screen
            graphics.PreferredBackBufferHeight = (int)WindowSize.Y + 100;
            graphics.PreferredBackBufferWidth = (int)WindowSize.X;
            Window.Title = "Map Editor";

            //Set the directory for the content
            Content.RootDirectory = "Content";

            //Set the file name for saving
            filename = Filename;

            //The map will consume most of the window, remember to leave room for the instructions
            Rectangle MapRect = new Rectangle(0, 100, (int)WindowSize.X, (int)WindowSize.Y);

            // Attempt to load the map
            try
            {
                map = new VirtualMap(filename, MapRect);
            }
            // If an exception occured, just create a blank map
            catch
            {
                //Create a new map based on the input
                map = new VirtualMap(RealSize, RealBlockSize, MapRect, BlockState.FREE);
            }

            //Set up the show string for instructions on how to edit
            instructionsString1 = new ShowString(new Vector2(0, 0), "Block Type: 1-Free     2-Blocked", 20,Color.Black);
            instructionsString2 = new ShowString(new Vector2(0, 30),"S-Save Map  Esc-Quit + Inc Brush Size - Dec Brush Size", 20, Color.Black);
            
            //Information about the current Brush selected
            currentBrush = new ShowString(new Vector2(0, 60),     "Current Brush: ", 20, Color.Black);
            currentBrushVar = new ShowString(new Vector2(200, 60), " BLOCKED ",20,Color.Red);

            //Information about the size of the brush
            currentBrushSize = new ShowString(new Vector2(400, 60), "Brush Size: ", 20, Color.Black);
            currentBrushSizeVar = new ShowString(new Vector2(540, 60), " SMALL ", 20, Color.Red);

            //Set the brush size to the smallest
            brushSize = BrushSize.SMALL;

            // Set what to draw when the user tries to draw
            brushState = BrushState.BLOCKED;

            // Draw the map for the first time
            redrawMap = true;
        }

        /// <summary>
        /// Change the state of blocks on the map focused around an x/y coordinate.
        /// Brush side affects how many blocks around the x/y are changed to the given state
        /// </summary>
        /// <param name="Position">The X/Y where the brush is centered on</param>
        /// <param name="WhichState">The state to change the block on the map to</param>
        public void Paint(Vector2 Position, BlockState WhichState, BrushSize brushSize)
        {
            // Only paint if the readonly flag is false
            if (map.ReadOnly())
                return;
            //Get the blocks under the mouse cursor, extending out according to brush size.
            // We expect to get back a square of blocks with a specific size
            int arraySize=0;
            Block[] blocks = map.GetPaddedBlock(Position, (int)brushSize, ref arraySize);

            for (int i = 0; i < arraySize; i++)
            {
                //If the block is out of bounds or already has the requested state do nothing
                if ((blocks[i].CurrentState != BlockState.OUT_OF_BOUNDS) && (blocks[i].CurrentState != WhichState))
                {
                    //Since we got here, we might as well change the state
                    blocks[i].CurrentState = WhichState;
                    // Map changed so the map  needs to be redrawn
                    redrawMap = true;
                }
            }
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // Initialize the states to the current keyboard/mouse states
            lastKeyboardInput = Keyboard.GetState();
            lastMouseInput = Mouse.GetState();

            // Make the mouse visible
            this.IsMouseVisible = true;
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            //spriteBatch = new SpriteBatch(GraphicsDevice);
            map.LoadContent(Content, GraphicsDevice);

            //Load the string content
            instructionsString1.LoadContent(Content, GraphicsDevice);
            instructionsString2.LoadContent(Content, GraphicsDevice);
            currentBrush.LoadContent(Content, GraphicsDevice);
            currentBrushVar.LoadContent(Content, GraphicsDevice);
            currentBrushSize.LoadContent(Content, GraphicsDevice);
            currentBrushSizeVar.LoadContent(Content, GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            map.Dispose();
            instructionsString1.Dispose();
            instructionsString2.Dispose();
            currentBrush.Dispose();
            currentBrushVar.Dispose();
            currentBrushSize.Dispose();
            currentBrushSizeVar.Dispose();
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            KeyboardState keyboardInput = Keyboard.GetState();
            MouseState mouseInput = Mouse.GetState();

            //Check if we want to quit
            if (keyboardInput.IsKeyDown(Keys.Escape))
                this.Exit();

            //Change the state of the editor based on the key that 
            // is selected which corresponds to the type of block
            // being drawn.
            // 1 for Free, 2 for blocked and 3 for unknown
            // Honestly I don't know why you would draw unknown squares.
            // But whatever.
            // BTW you can also save your map.

            // When the user pushes one, set the brush to draw empty squares
            if (keyboardInput.IsKeyDown(Keys.D1))
            {
                // Change the brush to empty squares
                brushState = BrushState.FREE;
                // Display that the free brush is in use
                currentBrushVar.SetString("FREE");
                // Redraw the screen
                redrawMap = true;
            }
            // When the user pushes two, set the brush to draw blocked squares
            else if(keyboardInput.IsKeyDown(Keys.D2))
            {
                // Set the brush to blocked squares
                brushState = BrushState.BLOCKED;
                // Display that the block brush is in use
                currentBrushVar.SetString("BLOCKED");
                // Redraw the screen
                redrawMap = true;
            }
            // When the user pushes S save the map to the file
            else if (keyboardInput.IsKeyDown(Keys.S))
            {
                // Save the map to the file
                map.SaveMap(filename);
                // Display that the map was successfully saved
                Popup.Show("Map saved to file " + filename + ".");
            }
            // When the user pushes '+' down, increase the brush size by one
            else if (keyboardInput.IsKeyDown(Keys.OemPlus) && lastKeyboardInput.IsKeyUp(Keys.OemPlus))
            {
                // Increment the brush sizes
                switch ((int)brushSize)
                {
                    // When the brush is small
                    case (int)BrushSize.SMALL:
                        // Increase brush size to medium
                        brushSize = BrushSize.MEDIUM;
                        // Display that the brush size is medium
                        currentBrushSizeVar.SetString("MEDIUM");
                        break;
                    // When the brush is medium
                    case (int)BrushSize.MEDIUM:
                        // Increase brush size to large
                        brushSize = BrushSize.LARGE;
                        // Display that the brush size is large
                        currentBrushSizeVar.SetString("LARGE");
                        break;
                    // When the brush is large
                    case (int)BrushSize.LARGE:
                        // The brush can't get any bigger, don't do anything
                        break;
                    // In case of invalid brush state, throw an exception
                    default: throw new Exception("Unknown Brush Size");
                }
                // Redraw the screen
                redrawMap = true;
            }
            // If the player pushes '-' reduce the brush size
            else if (keyboardInput.IsKeyDown(Keys.OemMinus) && lastKeyboardInput.IsKeyUp(Keys.OemMinus))
            {
                // Decrement the brush sizes
                switch ((int)brushSize)
                {
                    // If the brush size is small
                    case (int)BrushSize.SMALL:
                        // The brush can't get any smaller so break
                        break;
                    // If the brush size is medium
                    case (int)BrushSize.MEDIUM:
                        // Reduce the brush size to small
                        brushSize = BrushSize.SMALL;
                        // Display the brush size
                        currentBrushSizeVar.SetString("SMALL");
                        break;
                    // If the brush size is large
                    case (int)BrushSize.LARGE:
                        // Reduce the brush size to medium
                        brushSize = BrushSize.MEDIUM;
                        // Display the new brush size
                        currentBrushSizeVar.SetString("MEDIUM");
                        break;
                    // If the brush size is invalid throw an exception
                    default: throw new Exception("Unknown Brush Size");
                }
                // Redraw the map with the new string
                redrawMap = true;
            }

            // If the left mouse button is down, draw to the map
            if (mouseInput.LeftButton == ButtonState.Pressed)
            {
                // Don't draw anything if the currsent brush is none
                if (brushState != BrushState.NONE)
                {
                    // Draw tiles to the map based on the mouse's location and the current brush
                    this.Paint(new Vector2(mouseInput.X, mouseInput.Y), (BlockState)brushState, brushSize);
                }
            }

            // Remember the current input as the input from the previous update
            lastKeyboardInput = keyboardInput;
            lastMouseInput = mouseInput;

            // Update the map and the game
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself and all objects that need to be drawn
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            // If the redraw flag isn't set, don't draw anything
            if (redrawMap == false)
                return;

            // Wipe the screen
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            // Draw everything
            map.Draw();
            instructionsString1.Draw();
            instructionsString2.Draw();
            currentBrush.Draw();
            currentBrushVar.Draw();
            currentBrushSize.Draw();
            currentBrushSizeVar.Draw();
            base.Draw(gameTime);

            // Done drawing, don't redraw the map next time draw is called
            redrawMap = false;
        }
    }
}
